﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Haplous/ModeProperty.cs
//  *
//  ***************************************************************************

#endregion

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading;

using Haplous.Correctness;
using Haplous.Properties;

using JetBrains.Annotations;

namespace Haplous.Core
{
    /// <summary>
    ///     A thread-safe sparse container of items keyed on mode.  The mode keys are validated against those registered with
    ///     the associated game.  If a mode key is used for which a property doesn't already exist then a new instance is
    ///     created.
    /// </summary>
    [DebuggerDisplay("ModeProperty: Name={Name}")]
    public class ModeProperty<T> : Disposable where T : class, IProperty<T>, new()
    {
        /// <summary>
        ///     The dictionary being wrapped.
        /// </summary>
        /// <remarks>
        ///     Concurrent access is controlled by the <see cref="_lock" /> field.
        /// </remarks>
        [NotNull]
        private readonly Dictionary<Mode, T> _dictionary = new Dictionary<Mode, T>();

        /// <summary>
        ///     The game to which keys must belong.
        /// </summary>
        [NotNull]
        private readonly Game _game;

        /// <summary>
        ///     A lock used to control concurrent access to the <see cref="_dictionary" /> field.
        /// </summary>
        [NotNull]
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

        /// <summary>
        ///     The name of the property represented.
        /// </summary>
        [NotNull]
        private readonly string _name;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ModeProperty{T}" /> class.
        /// </summary>
        /// <param name="game">The game to which the keys must belong.</param>
        /// <param name="name">The name of the property represented.  The name may not be <see langword="null" /> or whitespace.</param>
        internal ModeProperty([NotNull] Game game, [NotNull] string name)
        {
            name.ValidateArgument("name", StringMustNotBe.NullOrEmptyOrWhiteSpace);
            game.ValidateArgument("game");

            _name = name;
            _game = game;
        }

        /// <summary>
        ///     The name of the property represented.
        /// </summary>
        [NotNull]
        public string Name
        {
            get { return _name; }
        }

        /// <summary>
        ///     Gets the value of this property for the current active mode.
        /// </summary>
        /// <value>The value of the property for the current active mode.</value>
        [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
        [NotNull]
        public T Value
        {
            get
            {
                var mode = _game.ActiveMode;

                if(mode == null)
                    // ReSharper disable once AssignNullToNotNullAttribute
                    throw new ModeException(string.Format(CultureInfo.InvariantCulture, Resources.Error_CannotSetPropertyNoActiveMode, _game.Name));

                return this[mode];
            }
        }

        /// <summary>
        ///     Gets the value of this property for the mode specified.
        /// </summary>
        /// <value>The value of the property for the mode specified.</value>
        [SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")]
        [NotNull]
        public T this[[NotNull] Mode mode]
        {
            get
            {
                mode.ValidateArgument("mode");

                _lock.EnterUpgradeableReadLock();
                try
                {
                    T item;
                    if(_dictionary.TryGetValue(mode, out item))
                        // ReSharper disable once AssignNullToNotNullAttribute
                        return item;

                    _lock.EnterWriteLock();
                    try
                    {
                        item = new T();
                        _dictionary.Add(mode, item);
                        return item;
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
                finally
                {
                    _lock.ExitUpgradeableReadLock();
                }
            }
        }

        /// <summary>
        ///     Copies the property values for the element in one mode to another mode.
        /// </summary>
        /// <param name="source">The source mode of the property values.</param>
        /// <param name="destination">The destination mode of the property values.</param>
        public void CopyModeProperties([NotNull] Mode source, [NotNull] Mode destination)
        {
            source.ValidateArgument("source");
            destination.ValidateArgument("destination");

            this[destination].Copy(this[source]);
        }

        /// <summary>
        ///     Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing">
        ///     <see langword="true" /> to release both managed and unmanaged resources;
        ///     <see langword="false" /> to release only unmanaged resources.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if(disposing)
            {
                _lock.Dispose();
                _dictionary.Clear();
            }

            base.Dispose(disposing);
        }
    }
}